package jsf;

import com.github.dvdme.ForecastIOLib.FIODataPoint;
import entity.Events;
import entity.Users;
import jsf.util.JsfUtil;
import jsf.util.PaginationHelper;
import session.UsersFacade;

import java.io.Serializable;
import static java.lang.System.out;
import java.util.Calendar;
import java.util.List;
import java.util.ResourceBundle;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import resource.WeatherWrapper;

@ManagedBean(name = "usersController")
@SessionScoped
public class UsersController implements Serializable {

    private Users current;
    private Users searched;
    private Users viewed;
    private List<Users> searchedList;
    private DataModel searchedElements;

    private DataModel items = null;
    @EJB
    private session.UsersFacade ejbFacade;
    private PaginationHelper pagination;
    private int selectedItemIndex;

    private EntityManager em;

    private String username;
    private String password;

    public UsersController() {
    }

    public Users getSelected() {
        if (current == null) {
            current = new Users();
            selectedItemIndex = -1;
        }
        return current;
    }

    public void setSelected(Users user) {
        current = user;
    }

    public Users getViewed() {
        return viewed;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public EntityManager getEntityManager() {
        return em;
    }

    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

    public Users getSearched() {
        if (searched == null) {
            searched = new Users();
        }
        return searched;
    }

    
    public String searchUsers() {
        searchedElements = null;
        searchedList = null;
        em = getFacade().getEntityManager();
        Query query = em.createNamedQuery("Users.findByUsername");
        query.setParameter("username", searched.getUsername());
        out.println("sto cercando " + searched.getUsername());
        searchedList = query.getResultList();
        if (!searchedList.isEmpty()) {
            out.println(searchedList.get(0).getUsername());
        }
        return "SearchResults";
    }

    public DataModel getSearchedElements() {
        if (searchedElements == null) {
            searchedElements = new ListDataModel(searchedList);
        }
        return searchedElements;
    }

    private UsersFacade getFacade() {
        return ejbFacade;
    }

    public void setFacade(UsersFacade uf) {
        ejbFacade = uf;
    }

    public PaginationHelper getPagination() {
        if (pagination == null) {
            pagination = new PaginationHelper(10) {

                @Override
                public int getItemsCount() {
                    return getFacade().count();
                }

                @Override
                public DataModel createPageDataModel() {
                    return new ListDataModel(getFacade().findRange(new int[]{getPageFirstItem(), getPageFirstItem() + getPageSize()}));
                }
            };
        }
        return pagination;
    }

    public String prepareList() {
        recreateModel();
        return "List";
    }

    public String prepareView() {
        current = (Users) getItems().getRowData();
        selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
        return "View";
    }

    public String prepareViewSearched() {
        viewed = (Users) getSearchedElements().getRowData();
        selectedItemIndex = pagination.getPageFirstItem() + getSearchedElements().getRowIndex();
        searchedElements = null;
        return "ViewSearched";
    }

    public String prepareCreate() {
        current = new Users();
        selectedItemIndex = -1;
        return "/index.xhtml";
    }

    public String create() {
        try {
            getFacade().create(current);
            //JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/resource/Bundle").getString("UsersCreated"));
            return prepareCreate();
        } catch (Exception e) {
            // JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/resource/Bundle").getString("PersistenceErrorOccured"));
            return null;
        }
    }

    public String login() {
        em = getFacade().getEntityManager();
        Query query = em.createNamedQuery("Users.findByLoginData");
        query.setParameter("username", current.getUsername());
        query.setParameter("password", current.getPassword());
        List<Users> results = query.getResultList();
        if (results.size() == 1) {
            current = results.get(0);
            username = current.getUsername();
            password = current.getPassword();
            out.println("Username e password sono: " + current.getUsername() + " " + current.getPassword());
            return "login.jsp";
        } else {
            return "errorLogin.xhtml";
        }
    }
    
    public void logout(){
        current = null;
    }

    public String prepareEdit() {
        current = (Users) getItems().getRowData();
        selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
        return "Edit";
    }

    public String update() {
        try {
            getFacade().edit(current);
            JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/resource/Bundle").getString("UsersUpdated"));
            return "View";
        } catch (Exception e) {
            JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/resource/Bundle").getString("PersistenceErrorOccured"));
            return null;
        }
    }

    public String destroy() {
        current = (Users) getItems().getRowData();
        selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
        performDestroy();
        recreatePagination();
        recreateModel();
        return "List";
    }

    public String destroyAndView() {
        performDestroy();
        recreateModel();
        updateCurrentItem();
        if (selectedItemIndex >= 0) {
            return "View";
        } else {
            // all items were removed - go back to list
            recreateModel();
            return "List";
        }
    }

    private void performDestroy() {
        try {
            getFacade().remove(current);
            JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/resource/Bundle").getString("UsersDeleted"));
        } catch (Exception e) {
            JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/resource/Bundle").getString("PersistenceErrorOccured"));
        }
    }

    private void updateCurrentItem() {
        int count = getFacade().count();
        if (selectedItemIndex >= count) {
            // selected index cannot be bigger than number of items:
            selectedItemIndex = count - 1;
            // go to previous page if last page disappeared:
            if (pagination.getPageFirstItem() >= count) {
                pagination.previousPage();
            }
        }
        if (selectedItemIndex >= 0) {
            current = getFacade().findRange(new int[]{selectedItemIndex, selectedItemIndex + 1}).get(0);
        }
    }

    public DataModel getItems() {
        if (items == null) {
            items = getPagination().createPageDataModel();
        }
        return items;
    }

    private void recreateModel() {
        items = null;
    }

    private void recreatePagination() {
        pagination = null;
    }

    public String next() {
        getPagination().nextPage();
        recreateModel();
        return "List";
    }

    public String previous() {
        getPagination().previousPage();
        recreateModel();
        return "List";
    }

    public SelectItem[] getItemsAvailableSelectMany() {
        return JsfUtil.getSelectItems(ejbFacade.findAll(), false);
    }

    public SelectItem[] getItemsAvailableSelectOne() {
        return JsfUtil.getSelectItems(ejbFacade.findAll(), true);
    }

    public Users getCreator() {
        em = getFacade().getEntityManager();
        Query query = em.createNamedQuery("Users.findByUsername");
        query.setParameter("username", username);
        out.println("L'username è: " + username);
        List<Users> results = query.getResultList();
        if (results.size() == 1) {
            current = results.get(0);
            return current;
        } else {
            return null;
        }
    }

    public List<Events> getEventsOfTheDay(Integer year, Integer month, Integer day, boolean onlyPublic) {
        em = getFacade().getEntityManager();
        List<Events> results = null;
        if (onlyPublic) {
            Query query = em.createNamedQuery("Users.findViewedEventsByDay");
            query.setParameter("userId", viewed);
            query.setParameter("year", year);
            query.setParameter("month", month);
            query.setParameter("day", day);
            results = query.getResultList();
        } else {
            Query query = em.createNamedQuery("Users.findCreatedEventsByDay");
            query.setParameter("user", current);
            query.setParameter("year", year);
            query.setParameter("month", month);
            query.setParameter("day", day);
            results = query.getResultList();
        }
        return results;
    }

    public List<Events> getParticipatedEventsOfTheDay(Integer year, Integer month, Integer day, boolean onlyPublic) {
        em = getFacade().getEntityManager();
        List<Events> results = null;
        if (onlyPublic) {
            Query query = em.createNamedQuery("Users.findViewedParticipatedEventsByDay");
            query.setParameter("user", viewed);
            query.setParameter("userId", viewed.getUserId());
            query.setParameter("year", year);
            query.setParameter("month", month);
            query.setParameter("day", day);
            results = query.getResultList();
        } else {
            Query query = em.createNamedQuery("Users.findParticipatedEventsByDay");
            query.setParameter("user", current);
            query.setParameter("userId", current.getUserId());
            query.setParameter("year", year);
            query.setParameter("month", month);
            query.setParameter("day", day);
            results = query.getResultList();
        }
        return results;
    }

    //The next method will return an HTML String containing the events for the user
    public String displayEvents(boolean onlyPublic) {
        //I need the day to start from
        Calendar cal = Calendar.getInstance();
        Integer currentMonth = cal.get(Calendar.MONTH);
        Integer currentYear = cal.get(Calendar.YEAR);
        String HTMLcode = "<div class='events'> Events created:";
        for (int currentDay = 1; currentDay < 32; currentDay++) {
            List<Events> eventList = getEventsOfTheDay(currentYear, currentMonth, currentDay, onlyPublic);
            Integer eventDay = null;
            for (Events temp : eventList) {
                if (eventDay != temp.getEventDay()) {
                    temp.updateWeatherInfo();
                    HTMLcode = HTMLcode + "<hr><br>Events for day: " + temp.getEventDay();
                    eventDay = temp.getEventDay();
                }
                if(!onlyPublic){
                HTMLcode = HTMLcode + "<div>" + "<strong>" + temp.getEventName()
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "Weather, desired: " + temp.getDesiredWeather() + ", forecasted: " + temp.getForcWeatherType()
                        + "</div><a href=\"views/events/viewEvent.jsp?event=" + temp.getEventId() + "\"><button>View event</button></a><br>";
                } else {
                    if(temp.getCreator().getCalendarPrivate()){
                        HTMLcode = HTMLcode + "<div>" + "<strong>Generic event"
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "</div><br>";
                    } else {
                        HTMLcode = HTMLcode + "<div>" + "<strong>" + temp.getEventName()
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "Weather, desired: " + temp.getDesiredWeather() + ", forecasted: " + temp.getForcWeatherType()
                        + "</div><a href=\"views/events/viewEvent.jsp?event=" + temp.getEventId() + "\"><button>View event</button></a><br>";
                    }
                }
            }

        }
        HTMLcode += "</div>";
        HTMLcode += "<br/><br/><div class='events'> Events to which you're participating:";
        for (int currentDay = 1; currentDay < 32; currentDay++) {
            List<Events> eventList = getParticipatedEventsOfTheDay(currentYear, currentMonth, currentDay, onlyPublic);
            Integer eventDay = null;
            for (Events temp : eventList) {
                if (eventDay != temp.getEventDay()) {
                    temp.updateWeatherInfo();
                    HTMLcode = HTMLcode + "<hr><br>Events for day: " + temp.getEventDay();
                    eventDay = temp.getEventDay();
                }
                if(!onlyPublic){
                HTMLcode = HTMLcode + "<div>" + "<strong>" + temp.getEventName()
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "Weather, desired: " + temp.getDesiredWeather() + ", forecasted: " + temp.getForcWeatherType()
                        + "</div><a href=\"views/events/viewEvent.jsp?event=" + temp.getEventId() + "\"><button>View event</button></a><br>";
                } else {
                    if(temp.getCreator().getCalendarPrivate()){
                        HTMLcode = HTMLcode + "<div>" + "<strong>Generic event"
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "</div><br>";
                    } else {
                        HTMLcode = HTMLcode + "<div>" + "<strong>" + temp.getEventName()
                        + "</strong><br> starting at: " + temp.getEventHour() + ":"
                        + temp.getEventMinute() + "   lasts: " + temp.getDuration() + "<br>"
                        + "Weather, desired: " + temp.getDesiredWeather() + ", forecasted: " + temp.getForcWeatherType()
                        + "</div><a href=\"views/events/viewEvent.jsp?event=" + temp.getEventId() + "\"><button>View event</button></a><br>";
                    }
                }
            }

        }
        HTMLcode += "</div>";
        return HTMLcode;
    }

    //Alert if there is an event with bad weather conditions
    public String displayAlert(boolean onlyPublic) {
        int cont = 0;
        Calendar cal = Calendar.getInstance();
        Integer currentMonth = cal.get(Calendar.MONTH);
        Integer currentYear = cal.get(Calendar.YEAR);
        Integer currentDay = cal.get(Calendar.DAY_OF_MONTH);
        //I have the current date. From now on I have to check the weather contions of all events
        String message = "";
        for (int day = currentDay; day < (currentDay+3); day++) {
            List<Events> eventList = getEventsOfTheDay(currentYear, currentMonth, day, onlyPublic);
            Integer eventDay = null;
            for (Events temp : eventList) {
                //The next condition will display the alert when the forecasted weather is different
                //from the desired one. We can improve it.
                if (temp.getForcWeatherType() == null) {
                    break; //No more weather conditions
                }
                if (temp.getOutdoor()) {
                    if (!(temp.getForcWeatherType().equals(temp.getDesiredWeather()))) {
                        cont++;
                        message += "The forecasted weather for the event " + temp.getEventName()
                                + " is " + temp.getForcWeatherType() + " while you would have preferred the weather "
                                + temp.getDesiredWeather() + ". ";
                        //Now I have to display the next date with the weather selected by the user
                        WeatherWrapper completeForecast = new WeatherWrapper();
                        FIODataPoint nextDay = new FIODataPoint();
                        completeForecast.setCity(temp.getLocation());
                        boolean weatherFound = false;
                        for (int numNextDay = day; numNextDay < 32; numNextDay++) {
                            try {
                                nextDay = completeForecast.getSpecificDay(numNextDay, day);
                            } catch (ArrayIndexOutOfBoundsException e) {
                                nextDay = null;
                            }
                            if (nextDay == null) {
                                message += "The system is not able to determine a following day with the weather condition "
                                        + temp.getDesiredWeather() + " in " + temp.getLocation();
                                break;
                            }
                            if (nextDay.icon().equals(temp.getDesiredWeather())) {
                                weatherFound = true;
                                message += "The next day with the weather condition " + temp.getDesiredWeather()
                                        + "is the " + numNextDay + "./n";
                                break;
                            }
                        }
                        if (!weatherFound) {
                            message += "The system is not able to determine a next day with the weather condition "
                                    + temp.getDesiredWeather() + " in " + temp.getLocation();
                        }
                    }
                }
            }
        }
        if (cont == 0) {
            return "No events with forecasted weather condition different from the one you chose";
        }
        return "<script>alert('" + message + "')</script>";
    }

    @FacesConverter(forClass = Users.class)
    public static class UsersControllerConverter implements Converter {

        @Override
        public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
            if (value == null || value.length() == 0) {
                return null;
            }
            UsersController controller = (UsersController) facesContext.getApplication().getELResolver().
                    getValue(facesContext.getELContext(), null, "usersController");
            return controller.ejbFacade.find(getKey(value));
        }

        java.lang.Integer getKey(String value) {
            java.lang.Integer key;
            key = Integer.valueOf(value);
            return key;
        }

        String getStringKey(java.lang.Integer value) {
            StringBuilder sb = new StringBuilder();
            sb.append(value);
            return sb.toString();
        }

        @Override
        public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
            if (object == null) {
                return null;
            }
            if (object instanceof Users) {
                Users o = (Users) object;
                return getStringKey(o.getUserId());
            } else {
                throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + Users.class.getName());
            }
        }

    }

}
